/**
 * @file bsp_iic.c
 * @author liufeng (liufeng@injoinic.com)
 * @brief software iic.
 * @version 0.1
 * @date 2024-10-31
 *
 * @copyright Copyright (c) 2024  injoinic
 *
 */

/* Include ------------------------------------------------------------------ */

#include "bsp_iic.h"
#include "bsp_delay.h"

/* Function ----------------------------------------------------------------- */

/**
 * @brief init the gpio for iic.
 */
void iic_init(void)
{
    // gpio外设初始化
    // SCL引脚初始化为推挽输出，上拉
    // SDA引脚初始化为开漏输出，上拉
}

/**
 * @brief deinit the gpio for iic.
 */
void iic_deinit(void)
{
    // gpio外设逆初始化
}

/**
 * @brief generate iic start sequence.
 */
static void iic_start(void)
{
    SCL_High;
    SDA_High; // iic bus idle.
    Delay_us(2);
    SDA_Low;  // SDA pin = 0.
    Delay_us(2);
    SCL_Low;  // SCL pin = 0.
}

/**
 * @brief generate iic restart sequence.
 */
static void iic_restart(void)
{
    SCL_Low;
    Delay_us(4);
    SCL_High;
    SDA_High; // iic bus idle.
    Delay_us(4);
    SDA_Low;  // SDA pin = 0.
    Delay_us(4);
    SCL_Low;  // SCL pin = 0.
}

/**
 * @brief generate iic stop sequence.
 */
static void iic_stop(void)
{
    SCL_Low;
    Delay_us(6);
    SDA_Low;  // SDA pin = 0.
    Delay_us(2);
    SCL_High; // SCL pin = 1.
    Delay_us(4);
    SDA_High; // SDA pin = 1.
}

/**
 * @brief send ACK to slave.
 */
static void iic_sendACK(void)
{
    SCL_Low;
    SDA_Low;
    Delay_us(4);
    SCL_High;
    Delay_us(4);
}

/**
 * @brief send NACK to slave.
 */
static void iic_sendNACK(void)
{
    SCL_Low;
    SDA_High;
    Delay_us(4);
    SCL_High;
    Delay_us(4);
}

/**
 * @brief read ACK/NACK from slave.
 * @return 0: ACK
 *         1: NACK
 */
static uint8_t iic_waitACK(void)
{
    SCL_Low;
    SDA_High; // set SDA = 1, to read SDA input.
    Delay_us(4);
    SCL_High;
    Delay_us(2);
    uint8_t res = SDA_Readpin;
    Delay_us(2);

    return res;
}

/**
 * @brief transmit a byte, MSB first.
 * @param[in] byte the byte to transmit.
 */
static void iic_transmitByte(uint8_t byte)
{
    for (uint8_t i = 0; i < 8; i++) {
        SCL_Low;
        Delay_us(2);
        if (byte & 0x80)
            SDA_High;
        else
            SDA_Low;
        Delay_us(2);
        SCL_High;
        Delay_us(4);
        byte = byte << 1;
    }
}

/**
 * @brief receive a byte, MSB first.
 * @return the byte received.
 */
static uint8_t iic_receiveByte(void)
{
    uint8_t byte = 0;
    SCL_Low;
    SDA_High; // set SDA = 1, to read SDA input.

    for (uint8_t i = 0; i < 8; i++) {
        byte = byte << 1;
        SCL_Low;
        Delay_us(4);
        SCL_High;
        Delay_us(2);
        byte |= SDA_Readpin;
        Delay_us(2);
    }
    return byte;
}

/**
 * @brief iic write bytes function.
 * @param[in] slave_addr slave address, shift as (0x0111000x), not include the w/#r control bit.
 * @param[in] buff write buffer.
 * @param[in] buff_len buffer length.
 * @return 0: successful
 *         1: fail
 */
uint8_t iic_write_bytes(uint8_t slave_addr, uint8_t* buff, uint8_t buff_len)
{
    iic_start();                      // iic start
    iic_transmitByte(slave_addr | 0); // send slave address
    if (iic_waitACK()) {              // check ACK or NACK from slave
        iic_stop();
        return 1;
    }

    for (uint32_t i = 0; i < buff_len; i++) {
        iic_transmitByte(*(buff + i)); // send value to writen.
        iic_waitACK();
    }
    iic_stop();
    return 0;
}

/**
 * @brief iic read bytes function.
 * @param[in] slave_addr slave address, shift as (0x0111000x), not include the w/#r control bit.
 * @param[in] buff read buffer.
 * @param[in] buff_len buffer length.
 * @return 0: successful
 *         1: fail
 */
uint8_t iic_read_bytes(uint8_t slave_addr, uint8_t* buff, uint8_t buff_len)
{
    iic_start();                      // iic start
    iic_transmitByte(slave_addr | 1); // send slave address
    if (iic_waitACK()) {              // check ACK or NACK from slave
        iic_stop();
        return 1;
    }

    for (uint32_t i = 0; i < buff_len; i++) {
        *(buff + i) = iic_receiveByte(); // receive byte to buffer.
        if (i < buff_len - 1) {
            iic_sendACK();
        }
    }
    iic_sendNACK();
    iic_stop();
    return 0;
}

/**
 * @brief iic write bytes to memory.
 * @param[in] slave_addr slave address, shift as (0x0111000x), not include the w/#r control bit.
 * @param[in] reg_addr register address.
 * @param[in] buff write buffer.
 * @param[in] buff_len buffer length.
 * @return 0: successful
 *         1: fail
 */
uint8_t iic_write_memory(uint8_t slave_addr, uint8_t reg_addr, uint8_t* buff, uint8_t buff_len)
{
    iic_start();                      // iic start
    iic_transmitByte(slave_addr | 0); // send slave address
    if (iic_waitACK()) {              // check ACK or NACK from slave
        iic_stop();
        return 1;
    }
    iic_transmitByte(reg_addr); // send register address.
    iic_waitACK();
    for (uint32_t i = 0; i < buff_len; i++) {
        iic_transmitByte(*(buff + i)); // send value to writen.
        iic_waitACK();
    }
    iic_stop();
    return 0;
}

/**
 * @brief iic read bytes from memory.
 * @param[in] slave_addr slave address, shift as (0x0111000x), not include the w/#r control bit.
 * @param[in] reg_addr register address.
 * @param[in] buff read buffer.
 * @param[in] buff_len buffer length.
 * @return 0: successful
 *         1: fail
 */
uint8_t iic_read_memory(uint8_t slave_addr, uint8_t reg_addr, uint8_t* buff, uint8_t buff_len)
{
    iic_start();                      // iic start
    iic_transmitByte(slave_addr | 0); // send slave address
    if (iic_waitACK()) {              // check ACK or NACK from slave
        iic_stop();
        return 1;
    }
    iic_transmitByte(reg_addr); // send register address.
    iic_waitACK();

    iic_restart();
    iic_transmitByte(slave_addr | 1); // send slave address
    iic_waitACK();
    for (uint32_t i = 0; i < buff_len; i++) {
        *(buff + i) = iic_receiveByte(); // receive byte to buffer.
        if (i < buff_len - 1) {
            iic_sendACK();
        }
    }
    iic_sendNACK();
    iic_stop();
    return 0;
}
